import fs from "fs-extra";
import _ from "lodash";
import path from "path";
import { copyFiles, indent } from "../tools/vendor.js";
import { ProtocolConvertOption, MacrosDef } from "../typings/index.js";
import { isComplexType } from "./def.js";
import { ProtocolStructParser } from "./ProtocolStructParser.js";
import { SvnHolder } from "./SvnHolder.js";
import { Xml2CSBytes } from "./Xml2CSBytes.js";
import { toolchain } from "../tools/toolchain.js";

export class Xml2Protocol {

    private static ins: Xml2Protocol;
    public static get Instance(): Xml2Protocol {
        if(!Xml2Protocol.ins) Xml2Protocol.ins = new Xml2Protocol();
        return Xml2Protocol.ins;
    }

    private protocolXmlPath!: string;
    private protocolPath!: string;
    private protocolPathsToSync: string[] = [];

    private needSafeprintf: boolean = false;
    private needLocalize = false;
    private logger: 'uts.logError' | 'console.error' = 'uts.logError';

    public async execute(): Promise<void> {
        const jsEncodeDecode = toolchain.projCfg.xml2protocol?.jsEncodeDecode === 'YES';
        const useCSBytes = toolchain.projCfg.xml2protocol?.useCSBytes === 'YES' || toolchain.projCfg.xml2protocol?.useCSBytes === 'YES_V2';
        console.log('use jsEncodeDecode?', jsEncodeDecode);
        console.log('use useCSBytes?', useCSBytes);

        if (toolchain.projCfg.Project.ProjectType === 'H5ts') this.logger = 'console.error';

        const temp = path.join(toolchain.options.workspace!, toolchain.options.project);
        this.protocolXmlPath = path.join(temp, 'protocolXml');
        this.protocolPath = path.join(temp, 'ts/protocol');
        const ssPath = path.join(temp, 'ss');
        const ssPathsToSync: string[] = [];
        const bytesPath = toolchain.projCfg.xml2protocol?.bytesFile || 'project/Assets/AssetSources/net/ss.bytes';
        // 更新svn
        await SvnHolder.Instance.checkSVN(!toolchain.options.debug, this.protocolXmlPath, toolchain.projCfg.Svn.SvrSvn + 'protocol');
        await this.initClientSVNs(jsEncodeDecode, toolchain.projCfg.Svn.ClientSvn, this.protocolPath, ssPath, bytesPath);
        if (toolchain.projCfg.Sync) {
            for (let i = 0, len = toolchain.projCfg.Sync.length; i < len; i++) {
                const pp = path.join(temp, `ts/protocolToSync${i}`);
                const sp = path.join(temp, `ssToSync${i}`);
                this.protocolPathsToSync.push(pp);
                ssPathsToSync.push(sp);
                await this.initClientSVNs(jsEncodeDecode, toolchain.projCfg.Sync[i], pp, sp, bytesPath);
            }
        }
        if (toolchain.projCfg.Project.ProjectType !== 'H5ts') {
            // unity项目还需chekcout cfg.json检查是否开启翻译
            const cfgPath = path.join(temp, 'cfg');
            await SvnHolder.Instance.checkSVN(!toolchain.options.debug, cfgPath, toolchain.projCfg.Svn.ClientSvn + 'project/platform/cfg.json');
            const cfg = await fs.readJson(path.join(cfgPath, 'cfg.json')) as { global?: { localize?: string }};
            if (cfg.global?.localize) {
                this.needLocalize = true;
            }
        }

        const allStructs = await ProtocolStructParser.Instance.readStructs(this.protocolXmlPath, toolchain.projCfg);

        // 读入黑名单，暂时不启用本功能，因为跳过某些字段，如果后台不配合删除，前台需要自己对buff进行偏移，很麻烦
        // const blacklistTxt = path.join(this.protocolPath, '.blacklist.txt');
        // await ProtocolStructParser.Instance.readBlacklist(blacklistTxt, (s) => s.endsWith('Request') || s.endsWith('Response') || s.endsWith('Notify'));

        const bodyDef = ProtocolStructParser.Instance.getStructDef('CSMsgBody')!;
        // structname -> msgid, etc: OpenBox_Notify -> MsgID_OpenBox_Notify
        const msgidMap: { [name: string]: string[] } = {};
        const rawRequests: string[] = [];
        const requestMap: { [name: string]: true } = {};
        const rspntfMap: { [name: string]: true } = {};
        const allVerRspntfMap: { [name: string]: true } = {};
        const notUsedCollections: string[] = [];
        for (const entry of bodyDef.entries) {
            if (!entry._attributes.value) continue;
            const et = entry._attributes.type;
            if (!et.endsWith('Request') && !et.endsWith('Response') && !et.endsWith('Notify')) {
                console.error(`[ERROR]Invalid type ${et}, should be Request/Response/Notify`);
                process.exit(1);
            }

            let msgIds = msgidMap[et];
            if (!msgIds) msgidMap[et] = msgIds = [];
            msgIds.push(entry._attributes.value!);

            const etDef = ProtocolStructParser.Instance.getStructDef(et)!;
            let clientUsed = etDef.options?.clientUsed, supportEncode = etDef.options?.supportEncode, decodeAllVersion = etDef.options?.decodeAllVersion;
            if (toolchain.projCfg.xml2protocol?.whitelist && !clientUsed) {
                notUsedCollections.push(et);
                continue;
            }

            if (et.endsWith('Request')) {
                rawRequests.push(et);
                this.collectComplexStructs(et, requestMap);
            }
            if (supportEncode) {
                this.collectComplexStructs(et, requestMap);
            }
            if (et.endsWith('Response') || et.endsWith('Notify')) {
                this.collectComplexStructs(et, rspntfMap);
                if (decodeAllVersion) {
                    this.collectComplexStructs(et, allVerRspntfMap);
                }
            }
        }
        rawRequests.sort();

        const orphans: string[] = [];
        // 处理孤儿结构
        for (const str of allStructs) {
            if (!requestMap[str] && !rspntfMap[str]) {
                const def = ProtocolStructParser.Instance.getStructDef(str);
                if (!def) {
                    console.error('No def for structure:', str);
                    continue;
                }
                if (def.options?.clientUsed) {
                    orphans.push(str);
                }
            }
        }

        const requests = Object.keys(requestMap);
        const requestIds: string[] = [];
        // 检查Response msgid
        for (const rqs of rawRequests)
        {
            requestIds.push(...msgidMap[rqs]);
        }
        const rspntf = Object.keys(rspntfMap);
        const allVerRspntf = Object.keys(allVerRspntfMap)
        requests.sort();
        rspntf.sort();
        allVerRspntf.sort();

        // Protocol.d.ts
        const structs = _.union(requests, rspntf, orphans);
        structs.sort();
        await this.convert(structs, jsEncodeDecode);

        // SendMsgUtil.ts
        await this.makeSendMsgUtil(rawRequests, msgidMap);

        // Macros.ts
        const macros = ProtocolStructParser.Instance.getAllMacros();
        await this.makeMacros(macros, requestIds);
        await this.makeMacrosDTS(macros, requestIds);

        if (jsEncodeDecode) {
            // decoder
            await this.makeDecodeBase();
            if (allVerRspntf.length > 0) {
                await this.makeLegacyDecoder(allVerRspntf, msgidMap);
            }
            await this.makeDecoder(rspntf, msgidMap);
            // encoder
            await this.makeEncoder(requests, msgidMap);
        }

        // ss.bin
        const bytesFile = path.basename(bytesPath);
        if (useCSBytes) {
            Xml2CSBytes.write(path.join(ssPath, bytesFile));
        } else if (!jsEncodeDecode){
            await fs.copyFile(path.join(this.protocolXmlPath, 'SS.bin'), path.join(ssPath, bytesFile));
        }

        // 提交svn
        if(!toolchain.options.debug) {
            console.log("Committing...");
            if (jsEncodeDecode) {
                await SvnHolder.Instance.commitSVN('xml2protocol@fy-convertor~', this.protocolPath);
            }
            if (!jsEncodeDecode || useCSBytes) {
                await SvnHolder.Instance.commitSVN('xml2protocol@fy-convertor~', this.protocolPath, ssPath);
            }

            // 同步
            if (toolchain.projCfg.Sync) {
                for (let i = 0, len = toolchain.projCfg.Sync.length; i < len; i++) {
                    console.log(`Syncing to ${toolchain.projCfg.Sync[i]}...`);
                    const pp = this.protocolPathsToSync[i];
                    await copyFiles(this.protocolPath, pp, '*.ts', ['NetHandler.ts', 'ProtocolUtil.ts']);
                    if (jsEncodeDecode) {
                        await SvnHolder.Instance.commitSVN('xml2protocol@fy-convertor~', pp);
                    }
                    if (!jsEncodeDecode || useCSBytes) {
                        const sp = ssPathsToSync[i];
                        await copyFiles(ssPath, sp, '*.bin');
                        await SvnHolder.Instance.commitSVN('xml2protocol@fy-convertor~', pp, sp);
                    }
                }
            }
        } else {
            console.log("Debug mode, committing skipped.");
        }

        // 输出not used
        if (notUsedCollections.length > 0) {
            console.log(`${notUsedCollections.length} structures omitted, add "convertOption='clientUsed'":`);
            console.log(`已忽略${notUsedCollections.length}个结构，如果需要转出，请添加"convertOption='clientUsed'"：`);
            console.log(notUsedCollections.join(', '));
        }
    }

    private async initClientSVNs(jsEncodeDecode: boolean, repository: string, protocolPath: string, ssPath: string, bytesPath: string): Promise<void> {
        if (toolchain.projCfg.Project.ProjectType === 'H5ts') {
            await SvnHolder.Instance.checkSVN(true, protocolPath, repository + (toolchain.projCfg.xml2protocol?.protocolRoot ?? 'project/src/automatic/protocol'));
        } else {
            await SvnHolder.Instance.checkSVN(true, protocolPath, repository + (toolchain.projCfg.xml2protocol?.protocolRoot ?? 'project/TsScripts/System/protocol'));
        }
        if (!jsEncodeDecode) {
            await SvnHolder.Instance.checkSVN(true, ssPath, toolchain.projCfg.Svn.ClientSvn + path.dirname(bytesPath));
        }
    }

    private collectComplexStructs(structName: string, map: { [name: string]: true }): void {
        if (map[structName]) return;
        map[structName] = true;
        const def = ProtocolStructParser.Instance.getStructDef(structName)!;
        for (const e of def.entries) {
            if (e._attributes.safeprintf) this.needSafeprintf = true;
            if (isComplexType(e._attributes.type)) {
                this.collectComplexStructs(e._attributes.type, map);
            }
        }
    }

    private async convert(structs: string[], jsEncodeDecode: boolean): Promise<void> {
        let tool = 'js';
        if (toolchain.projCfg.xml2protocol!.useCSBytes === 'YES_V2') {
            tool = 'ss.bytes，版本2（支持safeprintf）';
        } else if (toolchain.projCfg.xml2protocol!.useCSBytes === 'YES') {
            tool = 'ss.bytes，版本1（不支持safeprintf）';
        }
        let content = `/**
 * 协议结构定义，由fy-convertor根据协议xml生成
 * @encode_decode ${tool}
*/
declare module Protocol {
    export type FyMsg<T = any> = {
        m_stMsgHead: MsgHead;        
        m_msgBody: T;
    }
    export type MsgHead = {
        m_shMsgVersion: number;        
        m_uiTimeStamp_Low: number;
        m_uiTimeStamp_High: number;
        m_uiMsgID: number;
        m_uiUin: number;
        m_iFeedback: number;
        m_uiBodyLength: number;
    }
`;
        for(let s of structs) {
            const code = ProtocolStructParser.Instance.makeStructCode(s, 'type');
            if (code) {
                content += indent(code);
            }
        }
        content += '}\n';
        await fs.writeFile(path.join(this.protocolPath, 'Protocol.d.ts'), content, 'utf-8');
    }

    private async makeSendMsgUtil(requests: string[], msgidMap: { [name: string]: string[] }): Promise<void> {
        let body = '';
        for(let s of requests) {
            const structDef = ProtocolStructParser.Instance.makeSendMsgCode(s, msgidMap[s]);
            if (structDef) body += indent(structDef);
        }
        const ver = ProtocolStructParser.Instance.getMacro('VERSION')!._attributes.value;
        const content = `import { Macros } from './Macros.js';
/**Request协议组包，由fy-convertor根据协议xml生成*/
export class SendMsgUtil {
    private static readonly _msg: Protocol.FyMsg = {m_stMsgHead:{m_shMsgVersion:${ver}, m_uiTimeStamp_Low:0, m_uiTimeStamp_High:0, m_uiMsgID:0, m_uiUin:0, m_iFeedback:0, m_uiBodyLength:0}} as Protocol.FyMsg;
    private static readonly _bodyObjects: Record<number, any> = {};

    static getCacheObj(msgid: number): object {
        return SendMsgUtil._bodyObjects[msgid];
    }
${body}
}`;
        await fs.writeFile(path.join(this.protocolPath, 'SendMsgUtil.ts'), content, 'utf-8');
    }

    private async makeDecodeBase(): Promise<void> {
        const importByte = this.getByteImportStatement('../../');
        let content: string;
        if (this.needSafeprintf) {
            if (this.needLocalize) {
                content = `${importByte}
import { ArrayUtil } from "System/utils/ArrayUtil.js";

export declare type DecodeFuncMap = { [msgId: number]: (body: any, byte: Byte) => typeof body };

export class DecodeBase {
    // 所有非UGC的string字段都要定义safeprintf字段，以便处理协议时自动进行翻译
    protected __safeprintf(raw: string): string {
        if (!raw || raw.search(/[\\u4e00-\\u9fa5]+/) < 0) return raw;
        const params: string[] = [];
        let str = raw.replace(/{(.+?)}/g, (substring: string, ...args: any[]) => {
            const cnt = params.length;
            params.push(args[0]);
            return \`{\${cnt}}\`;
        });
        // 注意{}是保留符号，若玩家UGC内容中包含{}，可能导致CS代码报错
        str = I18N.I18NMgr.TranslatePrintf(str, ArrayUtil.toStringArray(params));
        return str;
    }
}
`;
            } else {
                content = `${importByte}
import { ArrayUtil } from "System/utils/ArrayUtil.js";

export declare type DecodeFuncMap = { [msgId: number]: (body: any, byte: Byte) => typeof body };

export class DecodeBase {
    // 所有非UGC的string字段都要定义safeprintf字段，以便处理协议时自动进行翻译
    protected __safeprintf(raw: string): string {
        if (!raw || raw.search(/[\\u4e00-\\u9fa5]+/) < 0) return raw;
        const params: string[] = [];
        let str = raw.replace(/{(.+?)}/g, (substring: string, ...args: any[]) => {
            const cnt = params.length;
            params.push(args[0]);
            return \`{\${cnt}}\`;
        });
        for (let i = 0, len = params.length; i < len; i++) {
            const p = params[i];
            if (p[0] == '^') {
                params[i] = p.substring(1);
            }
        }
        str = uts.format(str, ...params);
        return str;
    }
}
`;
            }
        } else {
            content = `${importByte}

export declare type DecodeFuncMap = { [msgId: number]: (body: any, byte: Byte) => typeof body };

export class DecodeBase {
}
`;
            
        }
        await fs.writeFile(path.join(this.protocolPath, 'DecodeBase.ts'), content, 'utf-8');
    }
    
    private async makeLegacyDecoder(rspntf: string[], msgidMap: { [name: string]: string[] }): Promise<void> {
        const ver = ProtocolStructParser.Instance.getMacro('VERSION')!._attributes.value;
        let listernContent: string = '';
        let decodeContent: string = '';
        for(const rn of rspntf) {
            if (ProtocolStructParser.Instance.structIgnored(rn)) continue;
            const msgIds = msgidMap[rn];
            if (msgIds) {
                const m = msgIds.map((v) => `this.map[${ProtocolStructParser.Instance.getMacro(v)!._attributes.value}]`).join(' = ');
                listernContent += `        ${m} = this._decode${rn}.bind(this);\n`;
            }

            decodeContent += indent(ProtocolStructParser.Instance.makeStructDecodeCode(rn), '    ');
        }

        const importByte = this.getByteImportStatement('../../../');
        let content = `${importByte}
import { DecodeBase, DecodeFuncMap } from '../DecodeBase.js';
import { DrUtil } from '../../../buf/DrUtil.js';
import { Macros } from '../Macros.js';
/**协议解码，由fy-convertor根据协议xml生成*/
export class DecodeUtil${ver} extends DecodeBase {
    public map: DecodeFuncMap = {};
    constructor() {
        super();
        // 使用bind(this)而非delegate，以便进行try catch
${listernContent}
    }

${decodeContent}
}
`;

        const p = path.join(this.protocolPath, 'legacy');
        let oldVer = Number(ver) - 1;
        while(oldVer > 0) {
            const oldFile = path.join(p, `DecodeUtil${oldVer}.ts`);
            if (fs.existsSync(oldFile)) {
                const oldContent = await fs.readFile(oldFile, 'utf-8');
                if (oldContent === content) {
                    // 和上一次的版本相同，则不重复生成
                    return;
                }
                // 只和最近的版本进行比较
                break;
            }
            oldVer--;
        }
        await fs.ensureDir(p);
        await fs.writeFile(path.join(p, `DecodeUtil${ver}.ts`), content, 'utf-8');
    }

    private async makeDecoder(rspntf: string[], msgidMap: { [name: string]: string[] }): Promise<void> {
        const importByte = this.getByteImportStatement('../../');
        const ver = ProtocolStructParser.Instance.getMacro('VERSION')!._attributes.value;
        let listernContent: string = '';
        let decodeContent: string = '';
        let legacyImports: string = '';
        for(const rn of rspntf) {
            if (ProtocolStructParser.Instance.structIgnored(rn)) continue;
            const msgIds = msgidMap[rn];
            if (msgIds) {
                const m = msgIds.map((v) => `this.map[${ProtocolStructParser.Instance.getMacro(v)!._attributes.value}]`).join(' = ');
                listernContent += `        ${m} = this._decode${rn}.bind(this);\n`;
            }

            decodeContent += indent(ProtocolStructParser.Instance.makeStructDecodeCode(rn), '    ');
        }

        // 加入legacy版本
        const legacyRoot = path.join(this.protocolPath, 'legacy');
        if (fs.existsSync(legacyRoot)) {
            const legacyVers: number[] = [];
            const files = await fs.readdir(legacyRoot);
            for (const f of files) {
                const mch = f.match(/DecodeUtil(\d+)/);
                if (mch != null) legacyVers.push(Number(mch[1]));
            }
            legacyVers.sort();

            let floor = 1;
            for (let lvi = 0, lvlen = legacyVers.length; lvi < lvlen; lvi++) {
                const legacyVer = legacyVers[lvi];
                legacyImports += `\nimport { DecodeUtil${legacyVer} } from './legacy/DecodeUtil${legacyVer}.js';`;
                if (legacyVer === floor) {
                    listernContent += `
        this.legacyMap[${legacyVer}] = new DecodeUtil${legacyVer}().map;`;
                } else {
                    listernContent += `
        const map${legacyVer} = new DecodeUtil${legacyVer}().map;
        for (let i = ${floor}; i <= ${legacyVer}; i++) {
            this.legacyMap[i] = map${legacyVer};
        }`;
                }
                floor = legacyVer + 1;
            }
        }

        let content = `${importByte}
import { DecodeBase, DecodeFuncMap } from './DecodeBase.js';${legacyImports}
import { DrUtil } from '../../buf/DrUtil.js';
import { Macros } from './Macros.js';
/**协议解码，由fy-convertor根据协议xml生成*/
export class DecodeUtil extends DecodeBase {
    private map: DecodeFuncMap = {};
    private legacyMap: { [ver: number]: DecodeFuncMap } = {};
    constructor() {
        super();
        // 使用bind(this)而非delegate，以便进行try catch
${listernContent}
    }
    
    public decodeMsgHead(msg: Protocol.FyMsg, byte: Byte): Protocol.FyMsg {
        if (!msg) msg = {} as Protocol.FyMsg;
        let body = msg.m_stMsgHead;
        if (!body) msg.m_stMsgHead = body = {} as Protocol.MsgHead;
        body.m_shMsgVersion = byte.getInt16();
        body.m_uiTimeStamp_Low = byte.getUint32();
        body.m_uiTimeStamp_High = byte.getUint32();
        body.m_uiMsgID = byte.getUint32();
        body.m_uiUin = byte.getUint32();
        body.m_iFeedback = byte.getInt32();
        body.m_uiBodyLength = byte.getUint32();
        return msg;
    }
    
    public decodeBody(msg: Protocol.FyMsg, byte: Byte): boolean {
        const func = this.map[msg.m_stMsgHead.m_uiMsgID];
        if (func) {
            try {
                msg.m_msgBody = func(msg.m_msgBody, byte);
            } catch(e) {
                ${this.logger}(\`协议\${msg.m_stMsgHead.m_uiMsgID}解包失败，前台版本号：${ver}，后台版本号：\${msg.m_stMsgHead.m_shMsgVersion}\`);
                if (e instanceof Error) ${this.logger}(e.stack);
                return false;
            }
            return true;
        } else {
            byte.pos = byte.pos + msg.m_stMsgHead.m_uiBodyLength;
        }
        return false;
    }

    public decodeBodyWithVersion(msgId: number, body: any, ver: number, byte: Byte): boolean {
        let map: DecodeFuncMap;
        if (ver === Macros.VERSION) {
            map = this.map;
        } else {
            map = this.legacyMap[ver];
            if (map == null) {
                ${this.logger}(\`没有对应版本的解包库：\${ver}\`);
                return false;
            }
        }
        const func = map[msgId];
        if (func) {
            try {
                func(body, byte);
            } catch(e) {
                ${this.logger}(\`协议\${msgId}解包失败，版本号：\${ver}\`);
                if (e instanceof Error) ${this.logger}(e.stack);
                return false;
            }
            return true;
        }
        return false;
    }

${decodeContent}
}
`;

        await fs.writeFile(path.join(this.protocolPath, 'DecodeUtil.ts'), content, 'utf-8');
    }

    private async makeEncoder(rspntf: string[], msgidMap: { [name: string]: string[] }): Promise<void> {
        const importByte = this.getByteImportStatement('../../');
        const ver = ProtocolStructParser.Instance.getMacro('VERSION')!._attributes.value;

        let listernContent: string = '';
        let encodeContent: string = '';
        for(const rn of rspntf) {
            if (ProtocolStructParser.Instance.structIgnored(rn)) continue;
            const msgIds = msgidMap[rn];
            if (msgIds) {
                const m = msgIds.map((v) => {
                    const macro = ProtocolStructParser.Instance.getMacro(v);
                    if (macro == null) {
                        const err = `[ERROR]No macro definition: ${v}, please check if CAPITALIZATION ERRORS!`;
                        console.error(err);
                        throw err;
                    }
                    return `this.map[${macro._attributes.value}]`
                }).join(' = ');
                if (toolchain.projCfg.Project.ProjectType === 'H5ts') {
                    listernContent += `        ${m} = Laya.Handler.create(this, this._encode${rn}, null, false);\n`;
                } else {
                    listernContent += `        ${m} = delegate(this, this._encode${rn});\n`;
                }
            }

            encodeContent += indent(ProtocolStructParser.Instance.makeStructEncodeCode(rn), '    ');
        }

        let func: string;
        let call: string;
        if (toolchain.projCfg.Project.ProjectType === 'H5ts') {
            func = 'Laya.Handler';
            call = 'func.runWith([body, byte])';
        } else {
            func = '(body: any, byte: Byte) => void';
            call = 'func(body, byte)';
        }

        let content = `${importByte}
import { DrUtil } from '../../buf/DrUtil.js';
import { Macros } from './Macros.js';
/**协议解码，由fy-convertor根据协议xml生成*/
export class EncodeUtil {
    private map: { [msgId: number]: ${func} } = {};
    constructor() {
${listernContent};
    }
        
    public encodeMsg(msg: Protocol.FyMsg, byte: Byte): void {
        const beginPos = byte.pos;//记录当前的起始位置
        this._encodeMsgHead(msg.m_stMsgHead, byte);//对头部进行编码
        this.encodeBody(msg.m_stMsgHead.m_uiMsgID, msg.m_msgBody, byte);
        DrUtil.directWriteUint(byte, beginPos + 22, byte.pos - beginPos - 26);//写入body的容量
    }

    private _encodeMsgHead(body: Protocol.MsgHead, byte: Byte): void {
        byte.writeInt16(body.m_shMsgVersion); // ${ver}
        byte.writeUint32(body.m_uiTimeStamp_Low);
        byte.writeUint32(body.m_uiTimeStamp_High);
        byte.writeUint32(body.m_uiMsgID);
        byte.writeUint32(body.m_uiUin);
        byte.writeInt32(body.m_iFeedback);
        byte.writeUint32(body.m_uiBodyLength);
    }

    public encodeBody(msgId: number, body: any, byte: Byte): void {
        const func = this.map[msgId];
        ${call};
    }

${encodeContent}
}
`;

        await fs.writeFile(path.join(this.protocolPath, 'EncodeUtil.ts'), content, 'utf-8');
    }

    private async makeMacros(macros: MacrosDef[], requestIds: string[]): Promise<void> {
        let macrosContent: string = '';
        let noResponseContent: string = '';
        for(let m of macros) {
            let comment = m._attributes.value;
            if (m._attributes.desc) {
                comment += ', ' + m._attributes.desc;
            }
            const e = ProtocolStructParser.Instance.getMacroRelatedEntry(m._attributes.name);
            if (e != null) {
                comment += ', ' + e.map((v) => `{@link Protocol.${v.struct}.${v.entry._attributes.name}}`).join(', ');
            }
            macrosContent += `    /**${comment}*/\n`;
            macrosContent += `    ${m._attributes.name} = ${m._attributes.value},\n`;

            if (requestIds.includes(m._attributes.name)) {                
                const rsp = m._attributes.name.replace(/Request$/, 'Response');
                const rspDef = ProtocolStructParser.Instance.getMacro(rsp);

                if (rspDef != null) {
                    if (Number(m._attributes.value) + 1 !== Number(rspDef._attributes.value))
                    {
                        noResponseContent += `        /**{@link ${m._attributes.name}} vs {@link ${rspDef._attributes.name}}*/
        ${m._attributes.value}: ${rspDef._attributes.value},
`;
                    }
                } else {
                    noResponseContent += `        /**{@link ${m._attributes.name}}*/
        ${m._attributes.value}: 0,
`;
                }
            }
        }

        let content = `/* eslint-disable @typescript-eslint/no-duplicate-enum-values */
/**协议使用的宏定义，由fy-convertor根据协议xml生成*/
export const enum Macros {
${macrosContent}
}

export class MsgIdMapper {
    private static readonly ResponseMap = {
${noResponseContent}
    };

    public static getResponseId(requestId: number): number {
        const mapId = MsgIdMapper.ResponseMap[requestId];
        return mapId == null ? requestId + 1 : 0;
    }
}
`;

        await fs.writeFile(path.join(this.protocolPath, 'Macros.ts'), content, 'utf-8');
    }

    private async makeMacrosDTS(macros: MacrosDef[], requestIds: string[]): Promise<void> {
        if (toolchain.projCfg.xml2protocol?.accurateUnion != 'YES') return;

        let macrosContent: string = '';
        for(let m of macros) {
            let comment = m._attributes.value;
            if (m._attributes.desc) {
                comment += ', ' + m._attributes.desc;
            }
            const e = ProtocolStructParser.Instance.getMacroRelatedEntry(m._attributes.name);
            if (e != null) {
                comment += ', ' + e.map((v) => `{@link Protocol.${v.struct}.${v.entry._attributes.name}}`).join(', ');
            }
            macrosContent += `    /**${comment}*/\n`;
            macrosContent += `    export type ${m._attributes.name} = ${m._attributes.value};\n`;
        }

        let content = `/* eslint-disable @typescript-eslint/no-duplicate-enum-values */
/**协议使用的宏定义，由fy-convertor根据协议xml生成*/
declare module Macros {
${macrosContent}
}
`;

        await fs.writeFile(path.join(this.protocolPath, 'Macros.d.ts'), content, 'utf-8');
    }

    private getByteImportStatement(rel: string): string {
        return toolchain.projCfg.Project.ProjectType === 'H5ts' ? 'import Byte = Laya.Byte;' : `import { Byte } from "${rel}buf/Byte.js";`;
    }
}